package test

import (
	"distributed_db/client"
	"distributed_db/monitor/raft"
	"distributed_db/utils"
	"fmt"
	"math/rand"
	"os/exec"
	"runtime"
	"sync"
	"testing"
	"time"
)

type Config struct {
	mu         sync.Mutex
	t          *testing.T
	n_server   int
	servers    []*raft.Server
	server_end []string  // ip port pair
	endnames   []string  // the port file names each sends to
	start      time.Time // time at which make_config() was called
	// begin()/end() statistics
	t0     time.Time // time at which test_test.go called cfg.begin()
	tables map[string]map[string]string
}

var ncpu_once sync.Once

func GeneratePorts(n int) []string {
	s := make([]string, n)
	mp := make(map[int]bool)
	cnt := 0
	for cnt < n {
		port := rand.Int()%1000 + 1200
		if _, exist := mp[port]; !exist {
			s[cnt] = fmt.Sprintf("%d", port)
			mp[port] = true
			cnt++
		}
	}
	return s
}

func GenerateTCP(ports []string) []string {
	s := make([]string, len(ports))
	for i := 0; i < len(ports); i++ {
		s[i] = "127.0.0.1:" + ports[i]
	}
	return s
}

func GeneratePeers(ports []string) []*utils.ClientEnd {
	clients := make([]*utils.ClientEnd, len(ports))
	for i := 0; i < len(ports); i++ {
		clients[i] = utils.MakeEnd("127.0.0.1:" + ports[i])
	}
	return clients
}

func makeConfig(t *testing.T, n_server int) *Config {
	ncpu_once.Do(func() {
		if runtime.NumCPU() < 2 {
			fmt.Printf("warning: only one CPU, which may conceal locking bugs\n")
		}
	})
	runtime.GOMAXPROCS(4)
	cfg := &Config{}
	cfg.t = t
	cfg.n_server = n_server
	cfg.servers = make([]*raft.Server, n_server)
	ports := GeneratePorts(n_server)
	cfg.endnames = ports
	cfg.server_end = GenerateTCP(ports)
	for i := range n_server {
		cfg.servers[i] = raft.MakeServer(GeneratePeers(ports), ports[i], i)
		cfg.servers[i].StartRaftService()
	}
	cfg.start = time.Now()
	cfg.tables = make(map[string]map[string]string)
	return cfg
}

func (cfg *Config) crash1(i int) {
	cfg.mu.Lock()
	defer cfg.mu.Unlock()
	cfg.servers[i].KillServer()
	cfg.servers[i] = nil
}

func (cfg *Config) start1(i int) {
	cfg.crash1(i)
	cfg.mu.Lock()
	cfg.servers[i] = raft.MakeServer(GeneratePeers(cfg.endnames), cfg.server_end[i], i)
	cfg.mu.Unlock()
	cfg.servers[i].StartRaftService()
}

func (cfg *Config) StartAll() {
	for i := 0; i < len(cfg.servers); i++ {
		if cfg.servers[i] != nil {
			cfg.start1(i)
		}
	}
}
func (cfg *Config) cleanup() {
	utils.LogC(utils.Green, "Cleaning up...\n")
	for i := 0; i < len(cfg.servers); i++ {
		if cfg.servers[i] != nil {
			cfg.servers[i].KillServer()
		}
	}
	cfg.checkTimeout()
	exec.Command("rm", "*.bin")
}

func (cfg *Config) checkTimeout() {
	// enforce a two minute real-time limit on each test
	if !cfg.t.Failed() && time.Since(cfg.start) > 120*time.Second {
		cfg.t.Fatal("test took longer than 120 seconds")
	}
}

func (cfg *Config) begin(description string) {
	fmt.Printf("%s ...\n", description)
	cfg.t0 = time.Now()
}

func (cfg *Config) end() {
	cfg.checkTimeout()
	if !cfg.t.Failed() {
		cfg.mu.Lock()
		t := time.Since(cfg.t0).Seconds() // real time
		cfg.mu.Unlock()

		fmt.Printf("  ... Passed --")
		fmt.Printf("  %4.1f\n", t)
	}
}

func (cfg *Config) checkOneLeader() int {
	for iters := 0; iters < 10; iters++ {
		ms := 450 + (rand.Int63() % 100)
		time.Sleep(time.Duration(ms) * time.Millisecond)
		if hasLeader, index := cfg.Leader(); hasLeader {
			return index
		}
	}
	return -1
}

func (cfg *Config) Leader() (bool, int) {
	cfg.mu.Lock()
	defer cfg.mu.Unlock()

	for i := 0; i < cfg.n_server; i++ {
		if cfg.servers[i] == nil {
			continue
		}
		_, is_leader := cfg.servers[i].GetState()
		if is_leader {
			return true, i
		}
	}
	return false, 0
}

func (cfg *Config) All() []int {
	cfg.mu.Lock()
	defer cfg.mu.Unlock()

	all := make([]int, cfg.n_server)
	for i := 0; i < cfg.n_server; i++ {
		all[i] = i
	}
	return all
}

// Create a clerk with clerk specific server names.
// Give it connections to all of the servers, but for
// now enable only connections to servers in to[].
func (cfg *Config) makeClient(to []int) *client.Client {
	cfg.mu.Lock()
	defer cfg.mu.Unlock()
	ends := make([]string, len(to))
	for i := 0; i < len(to); i++ {
		ends[i] = cfg.server_end[to[i]]
	}
	c, _ := client.NewClient(ends)
	return c
}
